#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/ioport.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>

#include <asm/io.h>
#include <asm/mmu_context.h>
#include <asm/pgalloc.h>
#include <linux/uaccess.h>
#include <asm/tlb.h>
#include <asm/tlbflush.h>
#include <asm/pgtable.h>

#include "csdn_algo.h"

MODULE_AUTHOR("LYFAN");
MODULE_DESCRIPTION("lyfan coder module testing");
MODULE_LICENSE("GPL");

static struct wxcoder_device_t *wxcoder_dev;
static bool loadok = false;
static bool readable = false;

/*
 * Having a close hook prevents vma merging regardless of flags.
 */
static void wzcoder_mapping_close(struct vm_area_struct *vma)
{
}

static vm_fault_t wzcoder_mapping_fault(struct vm_fault *vmf)
{
	struct vm_area_struct *vma = vmf->vma;
	pgoff_t pgoff;
	struct page **pages;

	debug("wzcoder_mapping_fault vmf->pgoff: %lu\n", vmf->pgoff);
	pages = vma->vm_private_data;
	for (pgoff = vmf->pgoff; pgoff && *pages; ++pages)
		pgoff--;

	if (*pages) {
		struct page *page = *pages;
		get_page(page);
		vmf->page = page;
		return 0;
	}

	return VM_FAULT_SIGBUS;
}

static const struct vm_operations_struct wzcoder_mapping_vmops = {
	.close = wzcoder_mapping_close,
	.fault = wzcoder_mapping_fault,
};

static loff_t wzalgo_seek(struct file *file, loff_t offset, int orig)
{
	loff_t ret;

	debug("wzalgo_seek - offset: %lld, orig: %d\n", offset, orig);
	inode_lock(file_inode(file));
	switch (orig) {
	case SEEK_CUR:
		offset += file->f_pos;
		/* fall through */
	case SEEK_SET:
		/* to avoid userland mistaking f_pos=-9 as -EBADF=-9 */
		if ((unsigned long long)offset >= wxcoder_dev->size) {
			ret = -EOVERFLOW;
			break;
		}
		file->f_pos = offset;
		ret = file->f_pos;
	//	force_successful_syscall_return();
		break;
	default:
		ret = -EINVAL;
	}
	inode_unlock(file_inode(file));
	return ret;
}

ssize_t wzalgo_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)
{
	int ret;

	if (false == readable || false == loadok){
		return -EINVAL;
	}

	if (len + *ppos > wxcoder_dev->size || !wxcoder_dev->vbase){
		return -EINVAL;
	}

	ret = copy_to_user(buf, wxcoder_dev->vbase + *ppos, len);
	*ppos += len;

	return len;
}

static inline bool is_cow_mapping(vm_flags_t flags)
{
	return (flags & (VM_SHARED | VM_MAYWRITE)) == VM_MAYWRITE;
}

static int wzalgo_mmap(struct file *filp, struct vm_area_struct *vma)
{
	unsigned long size = PAGE_ALIGN(vma->vm_end - vma->vm_start);
	unsigned long addr, end, pgoff;
	int i = 0, err;

	if (false == readable || false == loadok){
		return -EINVAL;
	}

	debug("wzalgo_mmap1 - start: 0x%lx, end: 0x%lx, size: %ld\n\tpgoff: %lu, flags: %lu\n", vma->vm_start, vma->vm_end, size, \
						vma->vm_pgoff, vma->vm_flags);
	if (!wxcoder_dev->vbase){
		return -EINVAL;
	}

	addr = vma->vm_start;
	end = vma->vm_end;
	pgoff = vma->vm_pgoff;
	for (i = 0; i < (PAGE_ALIGN(size) >> PAGE_SHIFT); i++){
		vma->vm_end = vma->vm_start + PAGE_ALIGN(PAGE_SIZE);
		err = remap_pfn_range(vma, vma->vm_start, page_to_pfn(wxcoder_dev->pages[i+pgoff]), PAGE_SIZE, vma->vm_page_prot);
		if (err){
			debug("remap_pfn_range: i - %d, %d, addr-%lu, end-%lu\n", i, err, vma->vm_start, vma->vm_end);
			break;
		}
		vma->vm_start += PAGE_SIZE;
	}

	if (is_cow_mapping(vma->vm_flags)){
		vma->vm_pgoff = page_to_pfn(wxcoder_dev->pages[pgoff]);
	}
	vma->vm_end = end;
	vma->vm_start = addr;
	debug("wzalgo_mmap2 - start: 0x%lx, end: 0x%lx, size: %ld\n\tpgoff: %lu\n", vma->vm_start, vma->vm_end, size, vma->vm_pgoff);

	vma->vm_ops = &wzcoder_mapping_vmops;
	vma->vm_private_data = (void *)wxcoder_dev->pages;

	return err;
}

int setup_csdn_algodev(struct file* filp, wx_algo_info *usr_data)
{
	int ret, i = 0, npages;

	if (usr_data->magic == 0x12345678){
		readable = true;
	} else{
		return -EFAULT;
	}
	if (loadok){
		return 0;
	}

	filp->f_inode->i_size = usr_data->algo_len;
	debug("filp->f_inode.i_size: %lld\n", filp->f_inode->i_size);
	npages = (usr_data->algo_len + PAGE_SIZE) / PAGE_SIZE;
	wxcoder_dev->npages = npages;
	wxcoder_dev->size = usr_data->algo_len;
	ret = -ENOMEM;
	wxcoder_dev->pages = kmalloc(sizeof(struct page *) * npages, GFP_KERNEL);
	if (!wxcoder_dev->pages)
		goto oom;
	for (i = 0; i < npages; i++) {
		struct page *p;
		p = alloc_page(GFP_KERNEL);
		if (!p){
			goto oom;
		}
		wxcoder_dev->pages[i] = p;
		if (copy_from_user(page_address(p), (const void __user *)usr_data->algo_start+i*PAGE_SIZE, 
								(usr_data->algo_len - i*PAGE_SIZE) > PAGE_SIZE ? PAGE_SIZE : (usr_data->algo_len - i*PAGE_SIZE))){
			debug("err copy %d pages, left: %d pages\n", i, npages - i);
			ret = -EFAULT;
			goto oom;
		}
	}

	wxcoder_dev->vbase = vmap(wxcoder_dev->pages, npages, 0, PAGE_KERNEL);
	if (!wxcoder_dev->vbase){
		ret = -EFAULT;
		goto oom;
	}

	loadok = true;

	return 0;

oom:
	for (; i > 0; i--){
		__free_page(wxcoder_dev->pages[i]);
	}
	wxcoder_dev->size = 0;
	wxcoder_dev->npages = 0;
	kfree(wxcoder_dev->pages);
	wxcoder_dev->pages = NULL;
	if (wxcoder_dev->vbase != NULL){
		vunmap(wxcoder_dev->vbase);
		wxcoder_dev->vbase = NULL;
	}
	printk("Cannot allocate mem\n");

	return ret;
}

/* Verifies that the pointer can be read and/or written to with the given size.
 * The user specifies the mode, either readonly, or not (read-write). */
static bool pl_access_ok(const void __user *arg, size_t size, bool readonly)
{
	// Note that VERIFY_WRITE implies VERIFY_WRITE, so read-write is handled
	if (!readonly && !access_ok(VERIFY_WRITE, arg, size)) {
		wxdrv_err("Argument address %p, size %zu cannot be written to.\n",
				arg, size);
		return false;
	} else if (!access_ok(VERIFY_READ, arg, size)) {
		wxdrv_err("Argument address %p, size %zu cannot be read from.\n",
				arg, size);
		return false;
	}
	
	return true;
}

static long int wzalgo_ioctl(struct file* file, unsigned int cmd, unsigned long arg)
{
	long ret = 0;
	wx_algo_info usr_data;
	int32_t traced;

	// Verify that this IOCTL is intended for our device, and is in range
	if (_IOC_TYPE(cmd) != ALGO_IOCTL_MAGIC) {
		wxdrv_err("IOCTL command magic number does not match.\n");
		return -ENOTTY;
	} else if (_IOC_NR(cmd) >= ALGO_NUM_IOCTLS) {
		wxdrv_err("IOCTL command is out of range for this device.\n");
		return -ENOTTY;
	}
	
	// Verify the input argument
	if ((_IOC_DIR(cmd) & _IOC_READ)) {
		if (!pl_access_ok((void __user *)arg, _IOC_SIZE(cmd), false)) {
			return -EFAULT;
		}
	} else if (_IOC_DIR(cmd) & _IOC_WRITE) {
		if (!pl_access_ok((void __user *)arg, _IOC_SIZE(cmd), true)) {
			return -EFAULT;
		}
	}

	switch (cmd)
	{
	case WX_ALGOMODULE_LOAD:
		if (copy_from_user(&usr_data, (const void __user *)arg, sizeof(wx_algo_info))){
			return -EFAULT;
		}
		ret = setup_csdn_algodev(file, &usr_data);
		break;
	case WX_DYN_LOAD:
		readable = false;
		break;
	case WX_TRACE_DECT:
		traced = current->ptrace;
		if (copy_to_user((void __user *)arg, &traced, sizeof(int32_t))){
			return -EFAULT;
		}
		break;
	default:
		ret = -EFAULT;
		break;
	}

	return ret;
}

static const struct file_operations wzalgo_fops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = wzalgo_ioctl,
	.mmap = wzalgo_mmap,
	.read = wzalgo_read,
	.llseek = wzalgo_seek,
};

static struct miscdevice coderdev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "wxcoder",
	.fops = &wzalgo_fops
};

static int __init wx_algodev_init(void)
{
	int ret;

	wxcoder_dev = kzalloc(sizeof(struct wxcoder_device_t), GFP_KERNEL);
	if (wxcoder_dev == NULL){
		debug("allocate wxcoder_device fail!\n");
		return -ENOMEM;
	}

	wxcoder_dev->size = 0;
	wxcoder_dev->npages = 0;
	wxcoder_dev->vbase = NULL;
	wxcoder_dev->pages = NULL;
	ret = misc_register(&coderdev);
	if (ret){
		debug("failed to register device %i\n", ret);
		misc_deregister(&coderdev);
		kfree(wxcoder_dev);
	} else{
		debug("register /dev/wxcoder succ!\n");
	}

	return ret;
}

static void __exit wx_algodev_exit(void)
{
	int i = wxcoder_dev->npages - 1;

	for (; i >= 0; i--){
		__free_page(wxcoder_dev->pages[i]);
	}
	kfree(wxcoder_dev->pages);
	if (wxcoder_dev->vbase != NULL){
		vunmap(wxcoder_dev->vbase);
		wxcoder_dev->vbase = NULL;
	}
	kfree(wxcoder_dev);
	misc_deregister(&coderdev);
	debug("unregister /dev/wxcoder!\n");
}

module_init(wx_algodev_init);
module_exit(wx_algodev_exit);



